package com.cskayan.javase.object._4hashcode;
/**
 * hashCode方法
 * 方法的声明：
 *        public native int hashCode();
 *      该方法会返回一个int数值，不需要参数，无需考虑访问权限
 *      且方法没有final修饰，可以被重写的方法
 *      当然它是一个本地方法，不需要方法
 *
 * 方法的作用：
 *       1.返回该对象的哈希码值。
 *          该方法的返回值，一个int整数，就是这个对象的哈希码值
 *
 *       2.支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 *
 * 哈希映射：
 *   化无限为有限，这就是哈希映射。
 *  在哈希映射当中，集合A和B之间的对应关系f，就是一种映射的规则，称之为哈希函数、哈希方法或者哈希算法等。
 * 而通过哈希算法，求得的集合B中的元素，求称之为哈希值。
 * 化无限为有限，这本身就是不现实的，更不可能强求映射是完全一一对应的。所以在哈希映射中，出现多对一是相当正常的，称之为"哈希冲突"。
 *
 * Java中的哈希映射：
 *      Java中的hashCode方法就是一个哈希映射的函数，表示一种哈希映射的规则
 *      在Java中这个方法需要某个类的对象来调用它，返回一个int整数
 *      某个类的对象是无限个的，而int整数的取值范围是有限的
 *      这就是化无限为有限
 *      hashCode方法的返回值，一个int整数，就是该对象的哈希值
 *      String --> int
 *      规则：字符串有多长，它的哈希值就是几
 *      "abc" --> 3
 *      "1234" --> 4
 *      这种哈希映射的规则，是OK的，但不合理，因为哈希冲突太多了
 *      "123" --> 3
 *
 * 哈希表：
 *      本质是一个数组，是一种存储key-value键值对的数据结构
 *      key-value都是一个对象
 *      通过计算key的一个哈希值，决定这个键值对在数组中的存储位置
 *      而一旦出现哈希冲突，就会出现多个键值对存储在一个数组存储单元的情况
 *      这样就需要使用链表将多个键值对"挂"起来
 *      显然，哈希冲突越多，哈希表的性能就越差
 *
 *
 * Object当中hashCode方法的默认实现：
 *      实际上，由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
 *      （这一般是通过将该对象的内部地址转换成一个整数来实现的，但是 JavaTM 编程语言不需要这种实现技巧。）
 *      它是通过一个本地方法计算对象的地址，得出一个十进制的int整数
 *      这个整数就可以近似的看成这个对象的地址值，
 *      根据哈希映射的规则，一般来说，两个不同的对象，两个独立的对象，它们的哈希值一般是不同的
 *      （如果出现哈希冲突，是可能出现不同对象，哈希值一样的情况）
 *
 *      虽然哈希冲突允许存在，但是一个对象，必然会对应一个哈希值。
 *      在Java中如何判断两个引用指向的对象是同一个对象呢？
 *      两个对象相等，这两个对象就是同一个对象
 *      所以在Java中，用equals方法判断两个引用指向的对象是同一个对象
 *          如果equals方法返回true，认为两个引用指向的对象相同，是一个对象，那么它们哈希值必须完全一模一样
 *          如果equals方法返回false，认为两个引用指向的对象不相同，不是同一个对象，这时不强制要求哈希值必须完全不同。
 *
 * 为什么Java代码中要求同时重写equals和hashCode方法，或者同时不重写呢？
 *      如果equals方法不重写，用地址比较对象相等
 *      如果hashCode方法不重写，也是根据对象的地址来计算哈希值的
 *          如果都不重写，就能满足上述规则
 *          equals方法返回true，它们的哈希值必须是一样的
 *          反之，如果重写了某一个方法，而不重写另一个，就会违背哈希映射的原则
 *     如果equals方法重写，用对象的成员变量取值比较对象相等
 *     那么hashCode方法也必须随之重写，也应该用成员变量的取值来重写hashCode方法
 *      就是重写，也必须用同样的条件重写，这样才符合映射的规则
 *
 * 如何重写hashCode方法？
 *      1. 在 Java 应用程序执行期间，在对同一对象多次调用 hashCode 方法时，
 *          必须一致地返回相同的整数，前提是将对象进行 equals比较时所用的信息没有被修改。
 *          说明，equals方法是大哥，equals方法说对象相等，哈希值就不能变。
 *          并且，它们用于判断对象相等和计算哈希值的条件应该是一模一样的
 *
 *
 *          2. 如果根据 equals(Object)方法，两个对象是相等的，
 *          那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。
 *          哈希映射
 *          equals方法认为相等的两个对象是集合A中的同一个对象
 *          必须要唯一映射到集合B的某个元素，不能出现一对多。
 *
 *          3. 如果根据 equals(java.lang.Object)方法，两个对象不相等，那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果。
 *          因为映射允许出现多对一，允许出现哈希冲突
 *
 *          但是，程序员应该意识到，为不相等的对象生成不同整数结果可以提高哈希表的性能。
 *          虽然完美是达不到的，但是追求完美的♥还是需要的
 *
 *  具体到hashCode方法的重写，一般来说，用idea自动生成足够了。如果有特殊需求，再去根据需求重写即可。
 *
 *  注意事项：
 *          1. 一旦重写hashCode方法后，默认的toString方法就不会再打印地址值了。
 *              这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值，重写后不再是地址值了。
 *              假如使用对象的成员变量取值来计算哈希值，那么toString默认打印的就是这个对象的十六进制“成员变量取值计算出来的哈希值”
 *
 *              总之建议如果重写了hashCode方法
 *              同时重写toString方法
 *
 * 2. 如果类中有引用数据类型成员变量，那么就继续调用它的hashCode方法，也就需要重写它的hashCode方法。
 *
 * @since 09:26
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        Student s3 = s1;
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
        System.out.println("-----------------------------");
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1.equals(str2));
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);

    }
}
class Student{
    int age;
    double score;
    String name;

    public Student() {
    }

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                ", name='" + name + '\'' +
                '}';
    }

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }*/
}
